package com.xayq.orap.oauth.authorize;

import org.apache.commons.lang.StringUtils;
import org.apache.oltu.oauth2.as.request.OAuthAuthzRequest;
import org.apache.oltu.oauth2.as.response.OAuthASResponse;
import org.apache.oltu.oauth2.common.error.OAuthError;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.OAuthResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;

import com.xayq.orap.model.ClientDetails;
import com.xayq.orap.oauth.MyOAuthAuthzRequest;
import com.xayq.orap.oauth.OAuthHandler;
import com.xayq.orap.utils.CommonUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import java.io.IOException;
import java.util.Set;


@Component
public abstract class AbstractAuthorizeHandler extends OAuthHandler {

    private static final Logger LOG = LoggerFactory.getLogger(AbstractAuthorizeHandler.class);

    protected static final String REQUEST_USERNAME = "username";
    protected static final String REQUEST_PASSWORD = "password";
    protected static final String OAUTH_LOGIN_VIEW = "/views/oauth_login";
    protected static final String OAUTH_APPROVAL_VIEW = "/views/oauth_approval";
    protected static final String REQUEST_USER_OAUTH_APPROVAL = "user_oauth_approval";
    
    protected MyOAuthAuthzRequest oauthRequest;

    protected boolean userFirstLogged = false;
    protected boolean userFirstApproved = false;


    protected boolean checkAndResponseValidateFailed(OAuthResponse oAuthResponse) {
        if (oAuthResponse != null) {
            LOG.debug("Validate OAuthAuthzRequest(client_id={}) failed", oauthRequest.getClientId());
            CommonUtils.writeOAuthJsonResponse(response, oAuthResponse);
            return true;
        }
        return false;
    }
    
    
    @Override
    protected String getClientId() {
        return oauthRequest.getClientId();
    }
    @Override
    protected String getRedirectURI() {
        return oauthRequest.getRedirectURI();
    }
    @Override
    protected Set<String> getScopes() {
        return oauthRequest.getScopes();
    }

    protected Authentication getAuthFromSession(){
    	HttpSession session = oauthRequest.request().getSession();
    	SecurityContext context= session.getAttribute("SPRING_SECURITY_CONTEXT")!=null?(SecurityContext) session.getAttribute("SPRING_SECURITY_CONTEXT"):SecurityContextHolder.getContext();
        Authentication auth = context.getAuthentication();
        return auth;
    }
    
    protected String getUsername() {
    	Authentication auth = getAuthFromSession();
        return auth==null?"":auth.getName();
    }
    
    protected boolean isUserAuthenticated() {
    	
    	Authentication auth = getAuthFromSession();
        return auth==null?false:auth.isAuthenticated();
    }

    protected boolean isNeedUserLogin() {
        return !isUserAuthenticated() && !isPost();
    }


    protected boolean goApproval() throws ServletException, IOException {
        if (userFirstLogged) {
            //go to approval
            LOG.debug("Go to oauth_approval, clientId: '{}'", clientDetails().getClientId());
            HttpServletRequest request = oauthRequest.request();
            request.getRequestDispatcher(OAUTH_APPROVAL_VIEW)
                    .forward(request, response);
            userFirstLogged =false;
            return true;
        }
        return false;
    }

    //true is submit failed, otherwise return false
    protected boolean submitApproval() throws IOException, OAuthSystemException {
        if (isPost()) {
            //submit approval
            HttpServletRequest request = oauthRequest.request();
            String oauthApproval = request.getParameter(REQUEST_USER_OAUTH_APPROVAL);
            if (!"true".equalsIgnoreCase(oauthApproval)) {
                //Deny action
                LOG.debug("User '{}' deny access", getUsername());
                responseApprovalDeny();
                return true;
            } else {
                userFirstApproved = true;
                return false;
            }
        }
        return false;
    }

    protected void responseApprovalDeny() throws IOException, OAuthSystemException {

    	HttpSession session = oauthRequest.request().getSession();
    	
        LOG.debug("After 'ACCESS_DENIED' call logout. user: {}", getUsername());
        session.removeAttribute("SPRING_SECURITY_CONTEXT");
    	
    	OAuthResponse oAuthResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_FOUND)
                .setError(OAuthError.CodeResponse.ACCESS_DENIED)
                .setErrorDescription("User denied access")
                .location(clientDetails().getRedirectUri())
                .setState(oauthRequest.getState())
                .buildQueryMessage();
        LOG.debug("'ACCESS_DENIED' response: {}", oAuthResponse);

        CommonUtils.writeOAuthQueryResponse(response, oAuthResponse);

        //user logout when deny
//        SecurityContextLogoutHandler sclh = new SecurityContextLogoutHandler();
//        sclh.logout(oauthRequest.request(), response, getAuthFromSession());
        
    }


    protected boolean goLogin() throws ServletException, IOException {
        if (isNeedUserLogin()) {
            //go to login
            LOG.debug("Forward to Oauth login by client_id '{}'", oauthRequest.getClientId());
            HttpServletRequest request = oauthRequest.request();
            request.getRequestDispatcher(OAUTH_LOGIN_VIEW)
                    .forward(request, response);
            return true;
        }
        return false;
    }


    //true is login failed, false is successful
    protected boolean submitLogin() throws ServletException, IOException {
        if (isSubmitLogin()) {
            //login flow
            try {
            	Authentication token = createUsernamePasswordToken();
                Authentication authentication = authenticationManager.authenticate(token); //调用loadUserByUsername
                SecurityContextHolder.getContext().setAuthentication(authentication);
                HttpSession session = oauthRequest.request().getSession();
                session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());

                LOG.debug("Submit login successful");
                this.userFirstLogged = true;
                return false;
            } catch (Exception ex) {
                //login failed
                LOG.debug("Login failed, back to login page too", ex);

                HttpServletRequest request = oauthRequest.request();
                request.setAttribute("oauth_login_error", true);
                request.getRequestDispatcher(OAUTH_LOGIN_VIEW)
                        .forward(request, response);
                return true;
            }
        }
        return false;
    }

    private Authentication createUsernamePasswordToken() {
        HttpServletRequest request = oauthRequest.request();
        String username = request.getParameter(REQUEST_USERNAME);
        String password = request.getParameter(REQUEST_PASSWORD);
        return new UsernamePasswordAuthenticationToken(username, password);
    }

    private boolean isSubmitLogin() {
        return !isUserAuthenticated() && isPost();
    }

    protected boolean isPost() {
        return RequestMethod.POST.name().equalsIgnoreCase(oauthRequest.request().getMethod());
    }

    public void handle(MyOAuthAuthzRequest oauthRequest, HttpServletResponse response) throws OAuthSystemException, ServletException, IOException {
    	this.oauthRequest = oauthRequest;
        this.response = response;
    	//validate
        if (checkValidateResponse(validate())) {
            return;
        }

        //Check need usr login
        if (goLogin()) {
            return;
        }

        //submit login
        if (submitLogin()) {
            return;
        }

        // Check approval
        if (goApproval()) {
            return;
        }

        //Submit approval
        if (submitApproval()) {
            return;
        }

        //handle response
        handleResponse();
    }
    
    public OAuthResponse validateSelf() throws OAuthSystemException {
    	
    	HttpSession session = oauthRequest.request().getSession();
    	
    	//ClientDetails clientDetails = clientDetails();
    	//validate client_secret
    	final String clientId = oauthRequest.getClientId();
        final String clientSecret = oauthRequest.getClientSecret();
        if (clientSecret == null || !clientSecret.equals(clientDetails.getClientSecret())) {
            return invalidClientSecretResponse();
        }
        
        if(session.getAttribute("CLIEND_ID")==null||(!clientId.equals(session.getAttribute("CLIENT_ID")+""))){
    		session.setAttribute("CLIENT_ID", clientId);
    		session.removeAttribute("SPRING_SECURITY_CONTEXT");
    	}
    	if(session.getAttribute("CLIEND_SECRET")==null||(!clientSecret.equals(session.getAttribute("CLIEND_SECRET")+""))){
    		session.setAttribute("CLIEND_SECRET", clientSecret);
    		session.removeAttribute("SPRING_SECURITY_CONTEXT");
    	}

        //validate redirect_uri
        final String redirectURI = getRedirectURI();
        if (redirectURI == null || !redirectURI.equals(clientDetails.getRedirectUri())) {
            LOG.debug("Invalid redirect_uri '{}' by response_type = 'code', client_id = '{}'", redirectURI, clientDetails.getClientId());
            return invalidRedirectUriResponse();
        }
        
        if(session.getAttribute("REDIRECT_URI")==null||(!redirectURI.equals(session.getAttribute("REDIRECT_URI")+""))){
    		session.setAttribute("REDIRECT_URI", redirectURI);
    		session.removeAttribute("SPRING_SECURITY_CONTEXT");
    	}

        //validate scope
        final Set<String> scopes = getScopes();
        if (!scopes.isEmpty() && excludeScopes(scopes, clientDetails)) {
            return invalidScopeResponse();
        }
        
        String scopeStr = StringUtils.join(scopes.toArray(), ";");
        if(session.getAttribute("SCOPES")==null||(!scopeStr.equals(session.getAttribute("SCOPES")+""))){
    		session.setAttribute("SCOPES", scopeStr);
    		session.removeAttribute("SPRING_SECURITY_CONTEXT");
    	}
        
        return validatePlus();
    }

    protected abstract OAuthResponse validatePlus() throws OAuthSystemException;

    //Handle custom response content
    protected abstract void handleResponse() throws OAuthSystemException, IOException;
}
